package codetree;


import java.util.List;
import java.util.ArrayList;

import java.io.File;
import java.io.FileWriter;

/**
 * Created by chenchi on 17/1/22.
 */
public class CodeTreeOperation {

    public CodeTreeOperation() {

    }

    //the following two methods are used to make the tree can be stored in mongodb
    public CodeTree setAllParentNodeToNull(CodeTree codeTree) {
        setParentNodeToNull(codeTree.getRoot());
        return codeTree;
    }

    public void setParentNodeToNull(TreeNode treeNode) {
        for (int i = 0; i < treeNode.getChildNodes().size(); i++) {
            treeNode.getChildNodes().get(i).setParentNode(null);
            setParentNodeToNull(treeNode.getChildNodes().get(i));
        }
    }

    //the following two methods are used to recover the tree from mongodb
    public CodeTree setAllParentNodeToNonNull(CodeTree codeTree) {
        setParentNodeToNonNull(codeTree.getRoot());
        return codeTree;
    }

    public void setParentNodeToNonNull(TreeNode treeNode) {
        for (int i = 0; i < treeNode.getChildNodes().size(); i++) {
            treeNode.getChildNodes().get(i).setParentNode(treeNode);
            setParentNodeToNonNull(treeNode.getChildNodes().get(i));
        }
    }

    //the following two methods are used to set serial number of each node in a codetree
    public void setSerialNumberofEachNode(CodeTree codeTree) {
        setSerialNumberofNode(codeTree.getRoot());
    }

    public void setSerialNumberofNode(TreeNode treeNode) {
        int index = 1;
        List<TreeNode> list = new ArrayList<TreeNode>();
        treeNode.setSerialNumber(index);
        list.add(treeNode);
        while (list.size() > 0) {
            List<TreeNode> tempList = new ArrayList<TreeNode>();
            for (int i = 0; i < list.size(); i++) {
                TreeNode node = list.get(i);
                for (int j = 0; j < node.getChildNodes().size(); j++) {
                    node.getChildNodes().get(j).setSerialNumber(++index);
                    tempList.add(node.getChildNodes().get(j));
                }
            }
            list.removeAll(list);
            list = tempList;
        }
    }

    // this method is used to generate a regular number representation of tree
    public List<Integer> regularization(CodeTree codeTree) {
        List<Integer> regularization = new ArrayList<Integer>();
        List<TreeNode> list = new ArrayList<TreeNode>();

        list.add(codeTree.getRoot());
        if (codeTree.getRoot().getChildNodes().size() == 0) {
            regularization.add(list.get(0).getSerialNumber());
        } else {
            while (list.size() > 0) {
                List<TreeNode> tempList = new ArrayList<TreeNode>();
                for (int i = 0; i < list.size(); i++) {
                    TreeNode node = list.get(i);
                    if (node.getChildNodes().size() > 0) {
                        regularization.add(node.getSerialNumber());
                    }
                    for (int j = 0; j < node.getChildNodes().size(); j++) {
                        regularization.add(node.getChildNodes().get(j).getSerialNumber());
                        tempList.add(node.getChildNodes().get(j));
                    }

                }
                list.removeAll(list);
                list = tempList;
            }
        }
        return regularization;
    }

    //this method is used to save regular number representation of tree in text file
    public void saveRegularizedTreeInFile(List<Integer> regularization, String filePath) throws Exception {
        String content = "";
        for (int i = 0; i < regularization.size(); i++) {
            content += regularization.get(i).toString() + " ";
        }
        content += "\r\n";

        File file = new File(filePath);
        try {
            if (!file.exists()) {
                file.createNewFile();
            }
            FileWriter writer = new FileWriter(filePath, true);
            writer.write(content);
            writer.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void saveRegularizedTreeInFile(List<Integer> regularization, FileWriter writer){
        String content = "";
        for (int i = 0; i < regularization.size(); i++) {
            content += regularization.get(i).toString() + " ";
        }
        try {
            content += "\r\n";
            writer.write(content);
            writer.flush();
        }catch(Exception e){
            e.printStackTrace();
        }
    }

    //this method is used to save standard prediction used in train process
    public void saveTrainingPredictionInFile(String prediction, String filePath) throws Exception {
        File file = new File(filePath);
        try {
            if (!file.exists()) {
                file.createNewFile();
            }
            FileWriter writer = new FileWriter(filePath, true);
            writer.write(prediction + "\r\n");
            writer.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }



    public void saveTrainingPredictionInFile(String prediction, FileWriter writer){
        try{
            writer.write(prediction + "\r\n");
            writer.flush();
        }catch(Exception e){
            e.printStackTrace();
        }
    }

    public String getTreeSentence(CodeTree codeTree, boolean isCompleteFlag){
        List<String> stringList = new ArrayList<String>();
        List<TreeNode> nodeList = new ArrayList<TreeNode>();
        nodeList.add(codeTree.getRoot());
        while (nodeList.size() > 0) {
            List<TreeNode> tempList = new ArrayList<TreeNode>();
            for (int i = 0; i < nodeList.size(); i++) {
                TreeNode node = nodeList.get(i);
                if(!isCompleteFlag) {
                    stringList.add(node.getSerialNumber() - 1, node.toString());
                }
                else{
                    stringList.add(node.getSerialNumber() - 1, node.getCompleteMethodDeclaration());
                }
                for (int j = 0; j < node.getChildNodes().size(); j++) {
                    tempList.add(node.getChildNodes().get(j));
                }

            }
            nodeList.removeAll(nodeList);
            nodeList = tempList;
        }
        String content = "";
        for (int i = 0; i < stringList.size(); i++) {
            content += stringList.get(i) + " ";
        }
        return content;
    }
    // this method is used to save related tree of String format according to regular number representation of tree in text file
    public void saveTreeStringFormatInFile(CodeTree codeTree, String filePath, boolean isCompleteFlag) throws Exception {
        List<String> stringList = new ArrayList<String>();
        List<TreeNode> nodeList = new ArrayList<TreeNode>();
        nodeList.add(codeTree.getRoot());
        while (nodeList.size() > 0) {
            List<TreeNode> tempList = new ArrayList<TreeNode>();
            for (int i = 0; i < nodeList.size(); i++) {
                TreeNode node = nodeList.get(i);
                if(!isCompleteFlag) {
                    stringList.add(node.getSerialNumber() - 1, node.toString());
                }
                else{
                    stringList.add(node.getSerialNumber() - 1, node.getCompleteMethodDeclaration());
                }
                for (int j = 0; j < node.getChildNodes().size(); j++) {
                    tempList.add(node.getChildNodes().get(j));
                }

            }
            nodeList.removeAll(nodeList);
            nodeList = tempList;
        }

        String content = "";
        for (int i = 0; i < stringList.size(); i++) {
            content += stringList.get(i) + " ";
        }
        content += "\r\n";

        File file = new File(filePath);
        try {
            if (!file.exists()) {
                file.createNewFile();
            }
            FileWriter writer = new FileWriter(filePath, true);
            writer.write(content);
            writer.close();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    public void saveTreeStringFormatInFile(CodeTree codeTree, FileWriter writer, boolean isCompleteFlag){
        List<String> stringList = new ArrayList<String>();
        List<TreeNode> nodeList = new ArrayList<TreeNode>();
        nodeList.add(codeTree.getRoot());
        while (nodeList.size() > 0) {
            List<TreeNode> tempList = new ArrayList<TreeNode>();
            for (int i = 0; i < nodeList.size(); i++) {
                TreeNode node = nodeList.get(i);
                if(!isCompleteFlag) {
                    stringList.add(node.getSerialNumber() - 1, node.toString());
                }
                else{
                    stringList.add(node.getSerialNumber() - 1, node.getCompleteMethodDeclaration());
                }
                for (int j = 0; j < node.getChildNodes().size(); j++) {
                    tempList.add(node.getChildNodes().get(j));
                }

            }
            nodeList.removeAll(nodeList);
            nodeList = tempList;
        }
        String content = "";
        for (int i = 0; i < stringList.size(); i++) {
            content += stringList.get(i) + " ";
        }
        try{
            content += "\r\n";
            writer.write(content);
            writer.flush();
        }catch(Exception e){

        }
    }

    public List<CodeTree> increaseTree(CodeTree codeTree) {
        List<CodeTree> tempTreeList = new ArrayList<>();
        List<CodeTree> treeList = new ArrayList<>();
        //copy codeTree
        for(int i = 0; i < codeTree.getTotalNumber(); i ++){
            CodeTree tree = new CodeTree();
            tempTreeList.add(tree.copyCodeTree(codeTree));
        }
        //set serial number of each copy tree
        for(int i = 0; i < tempTreeList.size(); i ++){
            setSerialNumberofEachNode(tempTreeList.get(i));
        }
        //increase tree
        for(int i = 0; i < codeTree.getTotalNumber(); i ++){
            CodeTree tree = tempTreeList.get(i);
            TreeNode node = tree.getTreeNode(i + 1);
            node.setParentNode(null);
            tree.setRoot(node);
            setSerialNumberofEachNode(tree);
        }
        //filter out the tree that contains only one node
        for(int i = 0; i < tempTreeList.size(); i ++){
            if(tempTreeList.get(i).getTotalNumber() > 1){
                treeList.add(tempTreeList.get(i));
            }
        }
        return treeList;
    }

    //this method is used to construct training tree data from a given tree(change the root node)
    public void constructTrainingTree(List<CodeTree> list, String treePath, String predictionPath,String classPath,String generationNodePath, String treeSentencePath, boolean isCompleteFlag) {
        for (int index = 0; index < list.size(); index++) {
            setSerialNumberofEachNode(list.get(index));
            List<List> result = constructTrainingTree(list.get(index), isCompleteFlag);
            List<CodeTree> treeList = result.get(0);
            List<String> predictionList = result.get(1);
            List<String> classList = result.get(2);
            List<TreeNode> generationNodeList = result.get(3);
            for (int i = 0; i < treeList.size(); i++) {
                try {
                    setSerialNumberofEachNode(treeList.get(i));
                    saveRegularizedTreeInFile(regularization(treeList.get(i)), treePath);
                    saveTrainingPredictionInFile(predictionList.get(i), predictionPath);
                    saveTrainingPredictionInFile(classList.get(i), classPath);
                    saveTrainingPredictionInFile(generationNodeList.get(i).getSerialNumber() + " " +generationNodeList.get(i).getCompleteMethodDeclaration() , generationNodePath);
                    saveTreeStringFormatInFile(treeList.get(i), treeSentencePath, isCompleteFlag);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }

    }

    //the following two methods are used to construct training tree and prediction from one complete code tree
    public List<List> constructTrainingTree(CodeTree codeTree, boolean isCompleteFlag) {
        List<List> result = new ArrayList<List>();
        List<CodeTree> treeList = new ArrayList(); // used to store result
        List<String> stringList = new ArrayList();
        List<String> classList = new ArrayList();
        List<TreeNode> generationNodeList = new ArrayList<>();
        List<TreeNode> nodeList = new ArrayList<TreeNode>() {
        }; // used to store tree node according to serial number
        List<TreeNode> temp = new ArrayList<TreeNode>(); // used to store tree node according to depth first order
        nodeList.add(codeTree.getRoot().getSerialNumber() - 1, codeTree.getRoot());
        temp.add(codeTree.getRoot());
        /*inital node list*/
        for (int i = 1; i < codeTree.getTotalNumber(); i++) {
            nodeList.add(i, null);
        }
        //System.out.println(nodeList.size());
        int numberOfTimes = 1;// used to count the number of loop
        while (numberOfTimes < codeTree.getTotalNumber()) {
            //System.out.println(codeTree.getTotalNumber() + "    " + numberOfTimes);
            List<TreeNode> trainingNodeList = new ArrayList<TreeNode>();//used to store the temporary tree nodes for code tree
            /*initial trainingNodeList*/
            for (int i = 0; i < codeTree.getTotalNumber(); i++) {
                trainingNodeList.add(i, null);
            }
            for (int i = 0; i < numberOfTimes; i++) {
                TreeNode node = new TreeNode();
                node.copyNode(temp.get(i));
                if (i > 0) {
                    int parentSerialNumber = temp.get(i).getParentNode().getSerialNumber();
                    node.setParentNode(trainingNodeList.get(parentSerialNumber - 1));
                    trainingNodeList.set(node.getSerialNumber() - 1, node);
                    trainingNodeList.get(parentSerialNumber - 1).getChildNodes().add(node);
                    if (i == numberOfTimes - 1) {
                        CodeTree trainingCodeTree = new CodeTree();
                        trainingCodeTree.setRoot(trainingNodeList.get(0));
                        treeList.add(trainingCodeTree);
                        generationNodeList.add(trainingNodeList.get(temp.get(i).getSerialNumber() - 1));
                        //System.out.println(i + "  " + temp.get(i));
                        depthFirst(nodeList, temp, stringList, classList,temp.get(i),isCompleteFlag);
                    }

                } else {
                    trainingNodeList.set(node.getSerialNumber() - 1, node);
                    if (numberOfTimes == 1) {
                        // System.out.println(temp.get(i));
                        depthFirst(nodeList, temp, stringList,classList, temp.get(i), isCompleteFlag);
                        CodeTree trainingCodeTree = new CodeTree();
                        trainingCodeTree.setRoot(trainingNodeList.get(0));
                        treeList.add(trainingCodeTree);
                        generationNodeList.add(trainingNodeList.get(temp.get(i).getSerialNumber() - 1));
                    }
                }

            }
            // System.out.println(trainingNodeList.size());
            numberOfTimes += 1;

        }
        result.add(treeList);
        result.add(stringList);
        result.add(classList);
        result.add(generationNodeList);
        // System.out.println(treeList.get(0).getRoot().getChildNodes().size());
        // System.out.println(stringList.size());
        return result;
    }

    public void depthFirst(List<TreeNode> nodeList, List<TreeNode> temp, List<String> stringList, List<String> classList,TreeNode node, boolean isCompleteFlag) {
        boolean flag = false;
        if (node.getChildNodes() != null) {
            for (int j = 0; j < node.getChildNodes().size(); j++) {
                if (nodeList.get(node.getChildNodes().get(j).getSerialNumber() - 1) == null) {
                    flag = true;
                    nodeList.set(node.getChildNodes().get(j).getSerialNumber() - 1, node.getChildNodes().get(j));
                    temp.add(node.getChildNodes().get(j));
                    if(!isCompleteFlag){
                        stringList.add(node.getChildNodes().get(j).toString());
                        classList.add(node.getChildNodes().get(j).getClassName());
                    }
                    else{
                        stringList.add(node.getChildNodes().get(j).getCompleteMethodDeclaration());
                        classList.add(node.getChildNodes().get(j).getCompleteClassName());
                    }
                    break;
                }
            }
        }
        if (!flag) {
            // System.out.println(node.getParentNode().toString());
            if (node.getParentNode() != null) {
                depthFirst(nodeList, temp, stringList,classList, node.getParentNode(),isCompleteFlag);
            }
        }
    }
}
